/*
  (C) 2005 Adam D. Bradley

  This program is free software; you can redistribute it and/or
  modify it under the terms of Version 2 of the GNU General Public 
  License.
  
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.
  
  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free
  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

package step.parse;

import snobject.MimeException;
import snobject.snObject;
import step.*;
import sxe.sensors.GenericSensor;
import java.io.*;
import org.w3c.dom.*;

public class Parser
{
	private static final String DELIMETER_NODE_PROGRAM_ID = ":";
	private static boolean bAppendProgramIDtoEachStepNode;
	
    public static final step.Graph Parse(java.io.File file, boolean bAppendProgramID)
    {
		Document d = null;
    	try {
			d = snbenchlib.XML.getDocumentBuilder().parse(file);
		} catch (java.io.IOException e) {
		    System.err.println(e.getMessage());
		    return null;
		} catch (org.xml.sax.SAXException e) {
		    System.err.println(e.getMessage());
		    return null;
		}
		
    	return ParseDoc(d,bAppendProgramID);
    }
	
    public static final step.Graph Parse(String xml, boolean bAppendProgramID)
    {
    	Document d = null;
    	ByteArrayInputStream bis = new ByteArrayInputStream(xml.getBytes());
    	try {
			d = snbenchlib.XML.getDocumentBuilder().parse(bis);
		} catch (java.io.IOException e) {
		    System.err.println(e.getMessage());
		    return null;
		} catch (org.xml.sax.SAXException e) {
		    System.err.println(e.getMessage());
		    return null;
		}
		
    	return ParseDoc(d, bAppendProgramID);
    }

    private static final step.Graph ParseDoc(Document d, boolean bAppendProgramID)
    {    			
    	bAppendProgramIDtoEachStepNode = bAppendProgramID;
		step.Graph result = null;
//		ArrayList<String> alIDRollBack = new ArrayList<String>();

		try {			
		    result = InterpretRoot(d);//,alIDRollBack);
		} catch (step.parse.SyntaxError e) {
		    System.err.println(e.getMessage());
		    /* repair ID space */
		    /*
		    for(int i=0;i<alIDRollBack.size();i++){
		    	String s = alIDRollBack.get(i);
		    	Node.DeRegisterID(s);
		    }
		    */
		}
		return result;
    }

    protected static final step.Graph InterpretRoot(Document doc) throws step.parse.SyntaxError 
    		//ArrayList al) throws step.parse.SyntaxError
    {
		doc.normalize();
		Element root = doc.getDocumentElement();
		if (root.getTagName() != "stepgraph")
		    throw new step.parse.SyntaxError("Root node [" + root.getTagName() + "] must be [stepgraph]");
		String id = root.getAttribute("id");
		if (id == null)
			id = "NoProvidedID" + String.valueOf((new java.util.Date()).getTime()) ;
		
		step.Graph g = step.Graph.newEmptyGraph(id);
		
		if (root.hasChildNodes()) {
			org.w3c.dom.Node n;
			for (n = root.getFirstChild() ; 
			     n != null ; 
			     n = n.getNextSibling()) {
				if (n.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
					AddNodes(g, null, (Element) n);
				}
			}
		}
	
		return g;
		
    }

    protected static final void AddNodes(step.Graph g,
					 step.Node parent,
					 //ArrayList alIDChanges,
					 org.w3c.dom.Element e) throws step.parse.SyntaxError
    {
	step.Node created = null;
	String sNodeID = null;
	boolean descend = true;
	//try {
		// update node ID if in SD context
	    sNodeID = e.getAttribute("id");
		if(bAppendProgramIDtoEachStepNode){
			if(sNodeID.indexOf(DELIMETER_NODE_PROGRAM_ID)==-1)
				sNodeID = g.GetID() + DELIMETER_NODE_PROGRAM_ID + sNodeID;
			e.setAttribute("id",sNodeID);
		
			/* fix target IDs */
			sNodeID = e.getAttribute("target");
			if(!sNodeID.equals("")){
				if(sNodeID.indexOf(DELIMETER_NODE_PROGRAM_ID)==-1)
					sNodeID = g.GetID() + DELIMETER_NODE_PROGRAM_ID + sNodeID;
				e.setAttribute("target",sNodeID);
			}
		}
		
	    if (e.getTagName().equals("exp")) {
	    	created = new step.ExpNode(e.getAttribute("id"),
					   e.getAttribute("opcode"));
	    } else if (e.getTagName().equals("cond")) {
			created = new step.ConditionalNode(e.getAttribute("id"));
	    } else if (e.getTagName().equals("trigger")) {
	    	created = new step.TriggerNode(e.getAttribute("id"));
	    } else if (e.getTagName().equals("level_trigger")) {
	    	created = new step.LevelTriggerNode(e.getAttribute("id"));
	    } else if (e.getTagName().equals("edge_trigger")) {
	    	created = new step.EdgeTriggerNode(e.getAttribute("id"));
	    } else if (e.getTagName().equals("last_trigger_eval")) {
		    created = new step.LastTriggerEval(e.getAttribute("id"),
						       e.getAttribute("target"));
	    } else if (e.getTagName().equals("read")) {
	    	String sOp = e.getAttribute("opcode");
	    	try{
	    		step.TriggerReadNode.ReadMode rm = 
	    			step.TriggerReadNode.ReadMode.parse(sOp);
	    		
	    		created = new step.TriggerReadNode(e.getAttribute("id"), rm);
	    	}catch(ArrayIndexOutOfBoundsException ex){
	    		/* default - strongest type */
			    created = new step.TriggerReadNode(
				    e.getAttribute("id"),
				    step.TriggerReadNode.ReadMode.NEXT);
	    	}
	    } else if (e.getTagName().equals("bufferedread")) {
	    	String sOp = e.getAttribute("opcode");
	    	try{
	    		step.TriggerReadNodeBuffered.ReadMode rm = 
	    			step.TriggerReadNodeBuffered.ReadMode.parse(sOp);
	    		
	    		created = new step.TriggerReadNodeBuffered(e.getAttribute("id"), rm);
	    	}catch(ArrayIndexOutOfBoundsException ex){
	    		/* default - strongest type */
			    created = new step.TriggerReadNodeBuffered(
				    e.getAttribute("id"),
				    step.TriggerReadNodeBuffered.ReadMode.FIFOB);
	    	}	    	
	    } else if (e.getTagName().equals("socket")) {
		    try {
			    created = step.SocketNode.MakeSocket(
				    e.getAttribute("id"), 
				    e.getAttribute("protocol"),
				    e.getAttribute("role"),
				    e.getAttribute("peerid"),
				    new java.net.URI(
					    e.getAttribute("peeruri")));
		    } catch (java.net.URISyntaxException ex) {
			    throw new step.parse.SyntaxError(
				    "URI Syntax Error in [" + 
				    e.getAttribute("peeruri") + "]");
		    }
	    } else if (e.getTagName().equals("splice")) {
	    	created = new step.SpliceNode(e.getAttribute("id"),
					      e.getAttribute("target"));
	    } else if (e.getTagName().equals("const")) {
	    	created = new step.ConstNode(e.getAttribute("id"));
	    } else if (e.getTagName().equals("value")) {
		    descend = false;
		    if (!e.hasChildNodes())
			    throw new SyntaxError(
				    "<value> node needs children!");
		    created = null;
		    for (org.w3c.dom.Node n = e.getFirstChild() ; 
			 n != null ;
			 n = n.getNextSibling()) {
			    if (n.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
				    org.w3c.dom.Element ee = (Element) n;
				    if (ee.getTagName().equals("snobject")) {
					    if (created != null)
						    throw new SyntaxError("<value> node should only contain one <snobject> child");
					    try {
						    snObject sno = snObject.xmlParse(ee);
						    created = new step.ValueNode(
						    	e.getAttribute("id"), sno);
					    } catch (MimeException ex) {
						    ex.printStackTrace();
						    created = null;
						    throw new SyntaxError("<snobject> produced no result, must contain a syntax error.");
					    }
				    } /**
				       * other object encoding cases can be caught 
				       * here in "else if ()" blocks.  
				       * Check for (created!=null) as above, then 
				       * work your magic. */
				    // SJJ - SMOD: Added flowtype parsing
						else if (ee.getTagName().equals("flowtype")) {
							if (created == null)
								throw new SyntaxError("<value> define <snobject> child before flowtypes at node "+created.GetID());
							FlowType flowtype = parseFlowType(ee);
							created.AddFlowType(flowtype);
						}
						/* ignore whitespace and unknown tags... */
			    }
		    }
	    } else if (e.getTagName().equals("flowtype")) {
	    		descend = false;
				// SJJ - SMOD: Added flowtype parsing
				if (parent!=null) {
					FlowType flowtype = parseFlowType(e);
					parent.AddFlowType(flowtype);
				}
				else {
					System.err.println("flowtype must have a parent"); 
				}
	    } else if (e.getTagName().equals("sensor")) {

	    	descend = false;
	    	GenericSensor gs = null;
	    	
	    	// get the gs out
	    	
		    for (org.w3c.dom.Node n = e.getFirstChild() ; 
			 n != null ;
			 n = n.getNextSibling()) {
			    if (n.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
				    org.w3c.dom.Element ee = (Element) n;
				    if (ee.getTagName().equals("device")) {
				    	gs = GenericSensor.xmlParse(ee);
				    }
			    }
		    }
		    
		    if(gs==null)
		    	System.err.println("could not parse generic sensor from sensor block.");
		    else
		    	created = new step.SensorNode(e.getAttribute("id"),gs);
		   								
	    } else {
	    	System.err.println("Unknown node [" + e.getTagName() + 
				   "], ignoring...");
	    }
	//} 
		
		if (created != null) {
			// add to parent
			String strBindTo = e.getAttribute("bindTo");
			if(strBindTo!=null){
				created.setBindTo(strBindTo);
			}
			
			if (parent != null) {
				//created.AddParent(parent);
				parent.AddChild(created);	
			}
			// add to g
			g.getWriteLock();
try{
			g.AddNode(created);
}
catch (step.DuplicateID ex) {
	g.releaseWriteLock();
	System.err.println("Duplicate ID found.  Throwing SyntaxError");
    ex.printStackTrace();
    throw new SyntaxError("Duplicate node ID within program");
}
			g.releaseWriteLock();
			
			// add ID for possible rollback
			// alIDChanges.add(created.GetID());
		}
		
		if (descend && e.hasChildNodes()) {
			org.w3c.dom.Node child = e.getFirstChild();
			while (child != null) {
				if (child.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
					if (created != null) {
						//AddNodes(g, created, alIDChanges, (Element) child);
						AddNodes(g, created, (Element) child);
					} else {
						/* ignore, re-parent up... */
						//AddNodes(g, parent, alIDChanges ,(Element) child);
						AddNodes(g, parent ,(Element) child);
					}
				}
				child = child.getNextSibling();
			}
		}
		return;
    }


	/**
	 * Parse a flowtype element within a step node
	 * 
	 * @param e The XML node containing the flowtype to parse
	 * @return FlowType object parsed from node e
	 * 
	 * @author  Jorge Londono
	 * @author  Sowmya Manjanatha
	 * @author  Jenny Han
	 */
	private static FlowType parseFlowType(Element e) {
		FlowType flowtype = new FlowType(e.getAttribute("label"));
		org.w3c.dom.Node ftchild=e.getFirstChild();
		while(ftchild!=null) {
			if (ftchild.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
				org.w3c.dom.Element ce = (org.w3c.dom.Element)ftchild;
				if ( ce.getTagName().equals("param") ) {
					FlowTypeParam ftp = new FlowTypeParam(ce.getAttribute("name"),ce.getAttribute("value"));
					flowtype.AddChild(ftp);
				}
			}
			ftchild = ftchild.getNextSibling();
		}
		return flowtype;
	}
	
	
}